/*
 * Copyright 2010, 2011 Matt Oliveri
 *
 * This file is part of JConfigGen
 *
 * JConfigGen is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JConfigGen is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JConfigGen.  If not, see <http://www.gnu.org/licenses/>.
 */

package atma.jconfiggen;

import java.io.PrintStream;
import java.util.List;
import java.util.Iterator;

/** The root of a Config AST.
 * A {@code Config} instance is the root of a Config AST that you are building, or that has been built for you.
 * It provides methods to generate the Java configurator code.
 * <p>
 * Semantics of {@code Config}:<br>
 * A {@code Config} represents the input Config as a whole.
 * The output configurator is basically a Java method which evaluates an expression and returns its result.
 * The public methods of {@code Config} kick off the process of generating the output configurator.
 * The default style of configurator is a public class with a single public static method called "make", which evaluates the expression.
 * This style of configurator can be output as a complete Java source code file with {@link #print(PrintStream) print()}.
 * Other styles are possible though by using the other {@code print*} methods.
 * Since the default configurator code is a complete Java source file, {@code Config} needs to know some things in order to generate it correctly:<ul>
 *   <li>The name of the class to generate</li>
 *   <li>The name of the package the generated class will be in</li>
 *   <li>A list of {@code import} directives the generated code will need in order to resolve names</li>
 *   <li>A list of method parameters the configurator should take</li>
 * </ul>
 * If you're building the {@code Config} yourself, you provide these things, as well as the expression to evaluate,
 *   in the {@linkplain #Config(String,String,List,List,Expr) constructor}.
 * </p>
 * <p>
 * <b>Important:</b> You should only make at most one call to either {@link #printBody(PrintStream,int) printBody()} or {@link #print(PrintStream) print()}.
 * Behavior is unspecified If you call either of those methods after either of them has already been called.
 * (It will probably output wrong code, but I haven't tried it.)
 * Probably, you will only want to call any {@code print*} methods after you're done building the AST.
 * </p>
 */
public final class Config
{
	private int tIndex;
	String genTemp() {return "_t" + tIndex++;}

	private final String pack;
	private final String clss;
	private final List<String> imps;
	private final List<Param> params;
	private final Expr exp;

	/** Construct a {@code Config}.
	 * @param p The package to put the generated class in, or null, if you don't want a package declaration. E.g.: {@code "com.acme.widget"}
	 * @param c The name of the generated class. E.g.: {@code "WidgetConfig"}
	 * @param i A list of import directives. Can be empty, but not null. E.g.: {@code "java.util.ArrayList"}, {@code "com.acme.doohickey.*"}
	 * @param a A list of {@link Param} to specify the parameters the configurator should take. Can be empty, but not null.
	 * @param e An {@link Expr} for the configurator to evaluate.
	 */
	public Config(String p,String c,List<String> i,List<Param> a,Expr e)
	{
		pack = p;
		clss = c;
		imps = i;
		params = a;
		exp = e;
	}

	/** Print just the package declaration and import directives.
	 * @param out The stream to print to.
	 * @throws NullPointerException if {@code out} is null, or if the list of imports passed to the constructor was null.
	 */
	public void printHeader(PrintStream out)
	{
		if (pack != null)
		{
			out.print("package ");
			out.print(pack);
			out.println(';');
			out.println();
		}
		if (imps.size() > 0)
		{
			for (String i:imps)
			{
				out.print("import ");
				out.print(i);
				out.println(';');
			}
			out.println();
		}
	}

	/** Print just the name of the generated class.
	 * @param out The stream to print to.
	 * @throws NullPointerException if {@code out} is null.
	 */
	public void printClassName(PrintStream out) {out.print(clss);}

	/** Print just the return type of the configurator.
	 * @param out The stream to print to.
	 * @throws NullPointerException if {@code out} is null, or if the AST has a null where it shouldn't.
	 * @throws IndexOutOfBoundsException if part of the AST wan't provided with enough {@link List} elements.
	 * @throws InvalidConfigException if an error in the Config prevents the return type from being determined.
	 */
	public void printType(PrintStream out) throws InvalidConfigException {exp.printType(out);}

	/** Print just the method parameter list, not including the parentheses.
	 * @param out The stream to print to.
	 * @throws NullPointerException if {@code out} is null, or if the list of params passed to the constructor was null.
	 */
	public void printParams(PrintStream out)
	{
		Iterator<Param> i = params.iterator();
		if (i.hasNext())
		{
			i.next().print(out);
			while (i.hasNext())
			{
				out.print(',');
				i.next().print(out);
			}
		}
	}

	/** Print just the body of the method, not including curly braces.
	 * @param out The stream to print to.
	 * @param tabs The number of tab characters to put at the beginning of each line in order to indent the method body properly.
	 * @throws NullPointerException if {@code out} is null, or if the AST has a null where it shouldn't.
	 * @throws IndexOutOfBoundsException if part of the AST wan't provided with enough {@link List} elements.
	 * @throws InvalidConfigException if there is an error in the Config. (Though not all kinds of errors will be detected.)
	 */
	public void printBody(PrintStream out,int tabs) throws InvalidConfigException
	{
		exp.printStats(out,tabs);
		Expr.pTabs(out,tabs);
		out.print("return ");
		exp.printExpr(out);
		out.println(';');
	}

	/** Print a complete Java source code file.
	 * The code will start with the {@linkplain #printHeader(PrintStream) header}, and define a public class with the specified {@linkplain #printClassName(PrintStream) name}
	 *   and a single public static method called "make" with a {@linkplain #printType(PrintStream) return type}, {@linkplain #printParams(PrintStream) parameters},
	 *   and a {@linkplain #printBody(PrintStream,int) body}.
	 * @param out The stream to print to.
	 * @throws NullPointerException if {@code out} is null, or if the AST (including this {@code Config} itself) has a null where it shouldn't.
	 * @throws IndexOutOfBoundsException if part of the AST wan't provided with enough {@link List} elements.
	 * @throws InvalidConfigException if there is an error in the Config. (Though not all kinds of errors will be detected.)
	 */
	public void print(PrintStream out) throws InvalidConfigException
	{
		printHeader(out);
		out.print("public class ");
		printClassName(out);
		out.println();
		out.println('{');
		out.print("\tpublic static ");
		printType(out);
		out.print(" make(");
		printParams(out);
		out.println(')');
		out.println("\t{");
		printBody(out,2);
		out.println("\t}");
		out.println('}');
	}
}
